//package com.shujia
//
//import org.junit.{Before, Test}
//
//import scala.collection.mutable.ListBuffer
//import scala.io.{BufferedSource, Source}
//
//class Demo26Student {
//
//  //定义三个全局变量 接收三份数据 在read_file函数中进行初始化
//  var students: List[Students] = _
//  var score: List[Score] = _
//  var subject: List[Subject] = _
//
//  /**
//   * 因为每一道题目都可能需要用到students.txt、score.txt、subject.txt的数据
//   * 所以提前先将三份数据读入
//   * 并将每份数据构建成对应的List
//   * 再定义三个样例类与这三份数据一一对应
//   * 最后得到三个List，每个List中元素的类型对应一个样例类
//   */
//  @Before
//  def read_file(): Unit = {
//    // 读取students.txt数据
//    val stu_bs: BufferedSource = Source
//      .fromFile("data/students.txt")
//
//    // 读取score.txt数据
//    val sco_bs: BufferedSource = Source
//      .fromFile("data/score.txt")
//
//    // 读取subject.txt数据
//    val sub_bs: BufferedSource = Source
//      .fromFile("data/subject.txt")
//
//    students = stu_bs
//      .getLines()
//      .toList // getLines()方法会返回迭代器，迭代器会缺失一些方法，故转成List
//      .map(line => {
//        // 对每条数据进行切分 构建Students样例类对象 最终返回一个由Students对象构成List
//        // 并顺带初始化全局变量students
//        val splits: Array[String] = line.split(",")
//        val id: Int = splits(0).toInt
//        val name: String = splits(1)
//        val age: Int = splits(2).toInt
//        val gender: String = splits(3)
//        val clazz: String = splits(4)
//        Students(id, name, age, gender, clazz)
//      })
//
//    score = sco_bs
//      .getLines()
//      .toList
//      .map(line => {
//        // 对每条数据进行切分 构建Score样例类对象 最终返回一个由Score对象构成List
//        // 并顺带初始化全局变量score
//        val splits: Array[String] = line.split(",")
//        val id: Int = splits(0).toInt
//        val subject_id: Int = splits(1).toInt
//        val score: Int = splits(2).toInt
//        Score(id, subject_id, score)
//      })
//
//    subject = sub_bs
//      .getLines()
//      .toList
//      .map(line => {
//        // 对每条数据进行切分 构建Subject样例类对象 最终返回一个由Subject对象构成List
//        // 并顺带初始化全局变量subject
//        val splits: Array[String] = line.split(",")
//        val subject_id: Int = splits(0).toInt
//        val subject_name: String = splits(1)
//        val subject_score: Int = splits(2).toInt
//        Subject(subject_id, subject_name, subject_score)
//      })
//
//    // 关闭文件
//    stu_bs.close()
//    sco_bs.close()
//    sub_bs.close()
//  }
//
//  @Test
//  // 打印三份数据
//  def printAll(): Unit = {
//    students.take(10).foreach(println)
//    score.take(10).foreach(println)
//    subject.take(10).foreach(println)
//  }
//
//  @Test
//  // 1、统计班级人数
//  def clazz_num(): Unit = {
//    students
//      .groupBy(stu => stu.clazz) // 按班级分组
//      .map(kv => (kv._1, kv._2.size)) // 统计班级人数
//      .foreach(println)
//
//
//  }
//
//  @Test
//  // 2、统计学生的总分
//  def sum_score(): Unit = {
//    score.groupBy(sco => sco.id) // 按id分组
//      .map(kv => {
//        val id: Int = kv._1
//        val scores: List[Score] = kv._2
//        val sum_score: Int = scores.map(s => s.score).sum // 统计总分
//        (id, sum_score)
//      })
//      .foreach(println)
//  }
//
//  @Test
//  // 1、统计年级排名前十学生各科的分数 [学号,学生姓名，学生班级，科目名，分数]
//  def sum_score_top10_stu(): Unit = {
//    // groupBy 之后的map如果也是返回kv格式的数据 则默认会生成一个map
//    // 取出总分排名前十的学生id
//    val top10ids: List[Int] = score.groupBy(sco => sco.id) // 按id分组
//      .map(kv => {
//        val id: Int = kv._1
//        val scores: List[Score] = kv._2
//        val sum_score: Int = scores.map(s => s.score).sum // 计算总分
//        (id, sum_score)
//      }).toList
//      // 按照总分倒序排列
//      .sortBy(-_._2)
//      // 取前10条数据
//      .take(10)
//      // 直接取出id
//      .map(_._1)
//
//    // 将score转成kv格式的Map集合
//    // 将id作为key Score对象作为value
//    val scoMap: Map[Int, List[(Int, Score)]] = score.map(sco => (sco.id, sco)).groupBy(_._1)
//    scoMap.foreach(println)
//
//    // 将subject_id作为key subject_name作为value 构建subMap
//    val subMap: Map[Int, String] = subject.map(sub => (sub.subject_id, sub.subject_name)).toMap
//    scoMap.foreach(println)
//
//    // 先从students中取出学生信息
//    students
//      .filter(stu => top10ids.contains(stu.id)) // 根据总分排名前十的学生id的List 过滤学生
//      // 使用 flatMap是因为一个学生对应6门科目成绩 相当于传入一条返回六条
//      // 返回的六条数据可以依次放在ListBuffer中 最后借助flatMap展开
//      .flatMap(stu => {
//        // 初始化一个ListBuffer用于保留最后的结果
//        // ListBuffer中每个元素的类型是一个5元组（同题目需要的5列结果类型一一对应）
//        val top10stuLB: ListBuffer[(Int, String, String, String, Int)] = ListBuffer[(Int, String, String, String, Int)]()
//        val id: Int = stu.id
//        val name: String = stu.name
//        val clazz: String = stu.clazz
//        // 根据学生id从scoMap中将该学生的6门科目的分数取出来 得到一个Score对象构成的List
//        val scores: List[Score] = scoMap(stu.id).map(_._2)
//        // 遍历每个学生的6门科目成绩
//        scores.foreach(sco => {
//          val stu_score: Int = sco.score
//          val subject_id: Int = sco.subject_id
//          // 根据科目id从subMap中取出科目名称
//          val sub_name: String = subMap(subject_id)
//          // 将最终结果以5元组的形式添加到ListBuffer中
//          top10stuLB.append((id, name, clazz, sub_name, stu_score))
//        })
//        // 将ListBuffer返回 flatMap会自动展开 相当于一条数据变6条
//        top10stuLB
//      }).foreach(println)
//
//  }
//
//  @Test
//  // 2、统计总分大于年级平均分的学生 [学号，姓名，班级，总分]
//  def sum_score_beyond_avg(): Unit = {
//
//    // 学生总分Map
//    // k:学号 v:总分
//    val id_sum_score: Map[Int, Int] = score.groupBy(sco => sco.id)
//      .map(kv => {
//        val id: Int = kv._1
//        val scores: List[Score] = kv._2
//        val sum_score: Int = scores.map(s => s.score).sum
//        (id, sum_score)
//      })
//
//    // 计算年级平均分
//    // id_sum_score是一个Map， 直接.values方法可以获取Map所有的value
//    val sum_score_avg: Double = id_sum_score.values.sum / id_sum_score.size.toDouble
//
//    // 以学生id作为key Students对象最为value 构建stuMap
//    val stuMap: Map[Int, Students] = students.map(stu => (stu.id, stu)).toMap
//
//    id_sum_score
//      .filter(kv => kv._2 > sum_score_avg) // 过滤出成绩大于年级平均分的学生
//      .map(kv => {
//        val id: Int = kv._1
//        val sum_score: Int = kv._2
//        // 根据学生id从stuMap中提取出学生信息
//        val stu: Students = stuMap(id)
//        val name: String = stu.name
//        val clazz: String = stu.clazz
//        (id, name, clazz, sum_score)
//      }).foreach(println)
//
//  }
//
//  @Test
//  // 3、统计每科都及格的学生 [学号，姓名，班级，科目，分数]
//  def every_subject_pass(): Unit = {
//    // 每门科目的及格分数Map
//    // k：科目id  v：二元组(科目名称，科目及格分数)
//    val sub_pass_map: Map[Int, (String, Double)] = subject.map(sub => {
//      val subject_id: Int = sub.subject_id
//      val subject_name: String = sub.subject_name
//      val sub_pass_score: Double = sub.subject_score * 0.6
//      (subject_id, (subject_name, sub_pass_score))
//    }).toMap
//
//    // 所有6门科目都及格的学生id
//    val every_sub_pass_stu_ids: List[Int] =
//      score
//        // 从sub_pass_map取出科目及格的分数，用分数与及格分数比较
//        // 将及格的数据过滤出来
//        .filter(sco => sco.score >= sub_pass_map(sco.subject_id)._2)
//        // 根据学生id分组
//        .groupBy(sco => sco.id)
//        // 统计每个学生还有多少条分数记录
//        .map(kv => (kv._1, kv._2.size))
//        // 当分数记录数不等于6时，说明该学生肯定由科目不及格
//        // 进而过滤出所有6门科目都及格的学生
//        .filter(_._2 == 6)
//        .keys // 得到所有6门科目都及格的学生id
//        .toList // 转成List
//
//    // 将id作为key Score对象作为value
//    val scoMap: Map[Int, List[(Int, Score)]] = score.map(sco => (sco.id, sco)).groupBy(_._1)
//
//
//    students
//      // 根据every_sub_pass_stu_ids 过滤出6门科目都及格的学生
//      .filter(stu => every_sub_pass_stu_ids.contains(stu.id))
//      .flatMap(stu => {
//        // 初始化一个ListBuffer用于保留最后的结果
//        // ListBuffer中每个元素的类型是一个5元组（同题目需要的5列结果类型一一对应）
//        val every_pass_stu_lb: ListBuffer[(Int, String, String, String, Int)] = ListBuffer[(Int, String, String, String, Int)]()
//        val id: Int = stu.id
//        val name: String = stu.name
//        val clazz: String = stu.clazz
//        // 根据学生id从scoMap中将该学生的6门科目的分数取出来 得到一个Score对象构成的List
//        val scores: List[Score] = scoMap(stu.id).map(_._2)
//        // 遍历每个学生的6门科目成绩
//        scores.foreach(sco => {
//          val stu_score: Int = sco.score
//          val subject_id: Int = sco.subject_id
//          // 根据科目id从subMap中取出科目名称
//          val sub_name: String = sub_pass_map(subject_id)._1
//          // 将最终结果以5元组的形式添加到ListBuffer中
//          every_pass_stu_lb.append((id, name, clazz, sub_name, stu_score))
//        })
//        // 将ListBuffer返回 flatMap会自动展开 相当于一条数据变6条
//        every_pass_stu_lb
//      }).foreach(println)
//
//  }
//
//  @Test
//  // 4、统计每个班级的前三名 [学号，姓名，班级，分数]
//  def clazz_sum_score_top3(): Unit = {
//    // 学生总分
//    val id_sum_score: Map[Int, Int] = score.groupBy(sco => sco.id)
//      .map(kv => {
//        val id: Int = kv._1
//        val scores: List[Score] = kv._2
//        val sum_score: Int = scores.map(s => s.score).sum
//        (id, sum_score)
//      })
//
//    students
//      // 根据学生id 从id_sum_score中获取学生总分
//      .map(stu => (stu.id, stu.name, stu.clazz, id_sum_score(stu.id)))
//      .groupBy(_._3) // 按照班级分组
//      // 每个班级都需要返回3条数据 需要使用flatMap展开
//      .flatMap(kv => {
//        val clazz: String = kv._1 // 班级
//        val stuList: List[(Int, String, String, Int)] = kv._2 // 该班级所有学生的信息及总分
//        // 排序
//        val top3stu: List[(Int, String, String, Int)] = stuList
//          .sortBy(-_._4) // 按照总分降序排列
//          .take(3) // 取前三名
//        top3stu
//      })
//      .foreach(println)
//  }
//
//  // 5、统计偏科最严重的前100名学生  [学号，姓名，班级，科目，分数]
//  @Test
//  def unbalanceTop100Stu(): Unit = {
//    val sub_score_map: Map[Int, Int] = subject
//      .map(sub => {
//        (sub.subject_id, sub.subject_score)
//      }).toMap
//
//    // 归一化：每门科目的满分不一样 故需要将其全部转换成百分制
//    // 例如：语文满分150 小明考了90分 转换成百分制就是 90 / 150 * 100 = 60分
//    val id_new_score: List[(Int, Float)] = score.map(sco => {
//      val id: Int = sco.id
//      val sub_score: Int = sub_score_map(sco.subject_id)
//      val new_score: Float = sco.score * 100 / sub_score.toFloat
//      (id, new_score)
//    })
//
//
//    // 计算每个学生的平均成绩 得到id_avg_map
//    // k：学生id  v：学生的平均成绩
//    val id_avg_map: Map[Int, Float] = id_new_score
//      .groupBy(kv => kv._1)
//      .map(kv => {
//        val id: Int = kv._1
//        val values: List[(Int, Float)] = kv._2
//        val sum: Float = values.map(_._2).sum
//        val avg: Float = sum / values.size
//        (id, avg)
//      })
//
//    val nobalanceIds: List[Int] = id_new_score.map(kv => {
//      val id: Int = kv._1
//      val score1: Float = kv._2
//      val avg: Float = id_avg_map(id)
//      (id, Math.pow(score1 - avg, 2))
//    })
//      .groupBy(_._1)
//      .map(kv => {
//        val id: Int = kv._1
//        val values: List[(Int, Double)] = kv._2
//        val variance: Double = values.map(_._2).sum / values.size
//        (id, variance)
//      })
//      .toList
//      .sortBy(-_._2)
//      .map(_._1)
//      .take(100)
//
//    val scoMap: Map[Int, List[(Int, Score)]] = score
//      .map(sco => {
//        (sco.id, sco)
//      }).groupBy(_._1)
//
//    val sub_name_map: Map[Int, String] = subject
//      .map(sub => {
//        (sub.subject_id, sub.subject_name)
//      }).toMap
//
//    students.filter(stu => nobalanceIds.contains(stu.id))
//      .flatMap(stu => {
//        val lb: ListBuffer[(Int, String, String, String, Int)] = ListBuffer[(Int, String, String, String, Int)]()
//        val id: Int = stu.id
//        val name: String = stu.name
//        val clazz: String = stu.clazz
//        val id_scores: List[(Int, Score)] = scoMap(id)
//        id_scores.foreach(id_score => {
//          val sco: Score = id_score._2
//          val score1: Int = sco.score
//          val sub_name: String = sub_name_map(sco.subject_id)
//          lb.append((id, name, clazz, sub_name, score1))
//        })
//        lb
//      }).foreach(println)
//
//  }
//
//  // 5、统计偏科最严重的前100名学生  [学号，姓名，班级，科目，分数]
//  @Test
//  def unbalanceTop100Student(): Unit = {
//    // 归一化
//    // 将语数英三门科目的成绩换算成百分制
//    // 先构建一个sub_map
//    // k ： subject_id
//    // v : subject_score
//    val sub_map: Map[Int, Int] = subject.map(sub => {
//      (sub.subject_id, sub.subject_score)
//    }).toMap
//
//    val id_new_score: List[(Int, Double)] = score.map(sco => {
//      val id: Int = sco.id
//      val stu_score: Int = sco.score
//      val subject_id: Int = sco.subject_id
//      val subject_score: Int = sub_map(subject_id)
//      // 将分数转换成百分制
//      val new_score: Double = stu_score.toDouble / subject_score * 100
//      (id, new_score)
//    })
//
//    // 计算平均分
//    val id_avg_score: Map[Int, Double] = id_new_score.groupBy(_._1)
//      .map(kv => {
//        val id: Int = kv._1
//        val scores: List[(Int, Double)] = kv._2
//        val avg_score: Double = scores.map(_._2).sum / scores.size.toDouble
//        (id, avg_score)
//      })
//
//    // 计算方差
//    val noBalanceStuTop100IDs: List[Int] = id_new_score.map(kv => {
//      val id: Int = kv._1
//      val avg_score: Double = id_avg_score(id)
//      val new_score: Double = kv._2
//
//      // 计算 (分数-平均分)^2
//      (id, Math.pow((new_score - avg_score), 2))
//    }).groupBy(_._1)
//      .map(kv => {
//        val id: Int = kv._1
//        val values: List[(Int, Double)] = kv._2
//        // 计算方差
//        val variance: Double = values.map(_._2).sum / values.size
//        (id, variance)
//      }).toList
//      .sortBy(-_._2)
//      .map(_._1)
//      .take(100)
//
//    // 将score转成kv格式的Map集合
//    // 将id作为key Score对象作为value
//    val scoMap: Map[Int, List[(Int, Score)]] = score.map(sco => (sco.id, sco)).groupBy(_._1)
//
//    // 将subject_id作为key subject_name作为value 构建subMap
//    val subMap: Map[Int, String] = subject.map(sub => (sub.subject_id, sub.subject_name)).toMap
//
//    // 先从students中取出学生信息
//    students
//      .filter(stu => noBalanceStuTop100IDs.contains(stu.id)) // 根据总分排名前十的学生id的List 过滤学生
//      // 使用 flatMap是因为一个学生对应6门科目成绩 相当于传入一条返回六条
//      // 返回的六条数据可以依次放在ListBuffer中 最后借助flatMap展开
//      .flatMap(stu => {
//        // 初始化一个ListBuffer用于保留最后的结果
//        // ListBuffer中每个元素的类型是一个5元组（同题目需要的5列结果类型一一对应）
//        val top10stuLB: ListBuffer[(Int, String, String, String, Int)] = ListBuffer[(Int, String, String, String, Int)]()
//        val id: Int = stu.id
//        val name: String = stu.name
//        val clazz: String = stu.clazz
//        // 根据学生id从scoMap中将该学生的6门科目的分数取出来 得到一个Score对象构成的List
//        val scores: List[Score] = scoMap(stu.id).map(_._2)
//        // 遍历每个学生的6门科目成绩
//        scores.foreach(sco => {
//          val stu_score: Int = sco.score
//          val subject_id: Int = sco.subject_id
//          // 根据科目id从subMap中取出科目名称
//          val sub_name: String = subMap(subject_id)
//          // 将最终结果以5元组的形式添加到ListBuffer中
//          top10stuLB.append((id, name, clazz, sub_name, stu_score))
//        })
//        // 将ListBuffer返回 flatMap会自动展开 相当于一条数据变6条
//        top10stuLB
//      }).foreach(println)
//
//
//  }
//}
//
//case class Students(id: Int, name: String, age: Int, gender: String, clazz: String)
//
//case class Score(id: Int, subject_id: Int, score: Int)
//
//case class Subject(subject_id: Int, subject_name: String, subject_score: Int)